Skip to content

Phase 7 Steps 3a-3c: config split + PUT /memories/config deprecation#18

Merged
ethanj merged 4 commits intoarchitecture2from
phase7-v1-parity
Apr 18, 2026
Merged

Phase 7 Steps 3a-3c: config split + PUT /memories/config deprecation#18
ethanj merged 4 commits intoarchitecture2from
phase7-v1-parity

Conversation

@ethanj
Copy link
Copy Markdown
Contributor

@ethanj ethanj commented Apr 18, 2026

Summary

Phase 7 Steps 3a, 3b, and 3c — completes the config-split deliverable that was descoped from Phase 1A. Documents the public/internal partition of runtime config, deprecates PUT /memories/config for production behind a startup-validated flag, and freezes provider/model selection as startup-only to match the underlying cache contract.

Gated by a v1→v2 functional-parity audit (Item 0) that confirmed zero production callers of the now-restricted routes. The audit doc lives in atomicmemory-research/docs/core-repo/readme/v1-v2-functional-parity-audit.md.

What changed

Step 3a — Partition runtime config (commit 6fe44cd)

  • src/config.ts declares SUPPORTED_RUNTIME_CONFIG_FIELDS (39 fields — infrastructure, provider/model selection, operator runtime, major feature toggles, cost/cache ops) and INTERNAL_POLICY_CONFIG_FIELDS (66 fields — tuning thresholds, experimental toggles).
  • Derived types SupportedRuntimeConfig / InternalPolicyConfig as Pick<> slices of RuntimeConfig — documentation, not threading constraint.
  • New fence test src/__tests__/config-partition.test.ts asserts disjointness, full coverage, no stray keys. Any new RuntimeConfig field must be tagged into one bucket or the test fails.
  • Re-exported from src/index.ts. docs/consuming-core.md explains the stability contract.

Step 3b — Deprecate PUT /memories/config for production (commit 056f95e)

  • New runtimeConfigMutationEnabled field on RuntimeConfig, parsed from CORE_RUNTIME_CONFIG_MUTATION_ENABLED (default false).
  • CoreRuntimeConfigRouteAdapter.current() surfaces the flag. Routes read it from a memoized startup snapshot — no per-request process.env reads (matches the workspace rule "config validated at startup, routes don't re-check").
  • PUT /memories/config returns 410 Gone when the flag is off. Production deploys inherit the safe default.
  • .env.test and .env.test.example set the flag to true so existing tests and local dev keep working. .env.example documents the opt-in.

Step 3c — Freeze provider/model selection as startup-only (commit d369726)

  • Removed embeddingProvider, embeddingModel, llmProvider, llmModel from updateRuntimeConfig() in src/config.ts.
  • Route rejects with 400 + rejected array if any of embedding_provider, embedding_model, llm_provider, llm_model appear in the body.
  • Rationale: the embedding/LLM provider caches in embedding.ts and llm.ts are fixed at first use. v1 accepted these fields but mid-flight mutation never took effect. Making them explicitly startup-only aligns the API contract with reality — a bug fix disguised as a deprecation.
  • Removed now-dead requireSupportedProvider and requireNonEmpty helpers.

Follow-up — Docs and live-response cleanup (commit bac6086)

  • docs/api-reference.md §PUT /memories/config rewritten to match the new contract (4 mutable fields, 400/410 responses with example payloads, startup-only guidance).
  • Success-response note updated — the old text claimed "Provider/model changes are applied in-memory" which contradicted the new 400 rejection path.

Out of scope

  • Item 3d (thread leaf-module config) — explicitly multi-PR per the plan (one leaf module per PR).
  • Item 4 (retrieval polish) — separate PR.
  • Item 2 (main cutover) — release decision, deferred until last per user direction.

Test plan

  • npx tsc --noEmit — clean
  • npm test — 963/963 pass (957 pre-branch + 6 new: 4 partition + 1 410 + 1 400)
  • fallow --no-cache — 0 above threshold, maintainability 90.9 (good)
  • memory-route-config-seam.test.ts — mutable/immutable behavior pinned
  • composed-boot-parity.test.ts — existing PUT path unchanged in dev mode

🤖 Generated with Claude Code

ethanj and others added 4 commits April 17, 2026 22:43
Introduces the public documentation split of v2's runtime config that
Phase 1A deferred. No behavior change.

- src/config.ts: declare SUPPORTED_RUNTIME_CONFIG_FIELDS (39 fields —
  infrastructure, provider/model selection, operator runtime, major
  feature toggles, cost/cache ops) and INTERNAL_POLICY_CONFIG_FIELDS
  (66 fields — thresholds, tuning knobs, experimental toggles). Derive
  SupportedRuntimeConfig / InternalPolicyConfig as Pick<> slices of
  RuntimeConfig so the split is type-level documentation without
  constraining threading.
- src/__tests__/config-partition.test.ts: new fence asserting the two
  partitions are disjoint, cover every RuntimeConfig field on the
  singleton, and reference no stray keys. Any new RuntimeConfig field
  must be tagged into one bucket or the test fails — prevents silent
  drift of the public contract.
- src/index.ts: re-export the two arrays and four derived types from
  the root package so consumers can reference SUPPORTED_RUNTIME_CONFIG_FIELDS
  as the authoritative list of what's stable.
- docs/consuming-core.md: new "Config surface: supported vs experimental"
  section pointing at the authoritative arrays and explaining the
  stability contract.

961/961 tests pass (957 before + 4 new partition tests).
tsc --noEmit clean. fallow --no-cache 0 above threshold.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gates PUT /memories/config behind a startup-validated flag. Production
deploys get 410 Gone; dev/test environments opt in via env var and keep
v1 behavior. Follows the workspace rule "config is validated at startup,
routes don't re-check at request time" — no NODE_ENV branching in the
route handler.

- src/config.ts: add runtimeConfigMutationEnabled to RuntimeConfig,
  parsed from CORE_RUNTIME_CONFIG_MUTATION_ENABLED (default false).
  Added to SUPPORTED_RUNTIME_CONFIG_FIELDS so the partition test keeps
  it honest.
- src/app/runtime-container.ts: CoreRuntimeConfigRouteAdapter.current()
  now includes runtimeConfigMutationEnabled. Default adapter populates
  from config singleton at construction time — memoized, not a per-
  request env read.
- src/routes/memories.ts: RuntimeConfigRouteSnapshot gains the field.
  PUT /memories/config checks configRouteAdapter.current()
  .runtimeConfigMutationEnabled; false → 410 Gone with a message
  pointing at the env var. true → existing behavior preserved.
- .env.test.example: documents the flag with CORE_RUNTIME_CONFIG_MUTATION_ENABLED=true
  for test/dev. .env.test (gitignored) should be updated locally.
- .env.example: documents the flag as opt-in; left commented out so
  production deploys inherit the safe default.
- src/__tests__/memory-route-config-seam.test.ts: MutableRouteConfig
  gains the field (set true for existing tests); new test asserts PUT
  returns 410 when the flag is flipped to false.
- docs/consuming-core.md: new "PUT /memories/config — dev/test only"
  section explaining the gate and dev-mode setup.
- atomicmemory-research/docs/core-repo/readme/readme-migration-from-v1.md:
  §8 updated to reflect the deprecation path and point at the env var.

962/962 tests pass (961 + 1 new 410 test). tsc --noEmit clean.
fallow --no-cache 0 above threshold.

Parity audit (see v1-v2-functional-parity-audit.md §2) confirmed zero
production callers of PUT /memories/config, so the deprecation is
parity-preserving by definition.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes embeddingProvider, embeddingModel, llmProvider, llmModel from
updateRuntimeConfig() and rejects them at the HTTP boundary with 400.
This is a bug fix disguised as a deprecation: v1 accepted these fields
but the embedding.ts / llm.ts provider caches are stateful and fixed at
first use, so mid-flight mutation never actually took effect. Making
them explicitly startup-only aligns the API contract with reality.

Parity audit (v1-v2-functional-parity-audit.md §4) confirmed no caller
relies on runtime provider/model mutation — both in-repo tests and the
vendored prototype UI only mutated thresholds / maxSearchResults, which
remain mutable in dev/test mode.

- src/config.ts: narrow RuntimeConfigUpdates to 4 mutable fields
  (similarityThreshold, audnCandidateThreshold,
  clarificationConflictThreshold, maxSearchResults). Delete the four
  provider/model blocks from updateRuntimeConfig(). Remove the now-dead
  requireSupportedProvider and requireNonEmpty helpers.
- src/app/runtime-container.ts: narrow CoreRuntimeConfigRouteAdapter
  .update() signature to match.
- src/routes/memories.ts: narrow RuntimeConfigRouteUpdates; add
  STARTUP_ONLY_CONFIG_FIELDS = ['embedding_provider', 'embedding_model',
  'llm_provider', 'llm_model'] and reject the PUT with 400 if any
  appear in the body, with a message pointing at the env vars.
- src/__tests__/memory-route-config-seam.test.ts: new test asserts
  400 rejection with rejected: ['embedding_provider'].
- docs/consuming-core.md + migration doc: document the startup-only
  constraint with the rationale.

963/963 tests pass (962 + 1 new 400 test). tsc --noEmit clean.
fallow --no-cache 0 above threshold.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex review caught two drift points between the implementation and the
docs/response payload:

- docs/api-reference.md §PUT /memories/config still documented the pre-
  Step-3b/3c contract — provider/model mutation fields and "all fields
  optional". Rewrote the section to: flag dev/test-only gating, list the
  4 currently-mutable fields, document the 400/410 responses with
  example payloads, and explain the startup-only rationale.
- src/routes/memories.ts: the success-response `note` contradicted the
  handler's own behavior. "Provider/model changes are applied in-memory
  for local experimentation" is no longer true (those fields are rejected
  earlier in the same handler). Replaced with an honest note pointing
  threshold-only mutation at dev use and startup restart for provider/
  model.

963/963 tests pass. tsc --noEmit clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ethanj ethanj merged commit f987dce into architecture2 Apr 18, 2026
@ethanj ethanj deleted the phase7-v1-parity branch April 18, 2026 18:12
ethanj added a commit that referenced this pull request Apr 19, 2026
Cuts over `main` to the Phase 1A–7 rearchitecture (composition root,
  explicit scope/observability contracts, store-narrowed repository access,
  public consumption seams, config split, leaf-module config threading,
  retrieval orchestration polish) plus the OSS-release-prep on top.

  ⚠️ Breaking changes for HTTP / SDK consumers
  - All API endpoints are now mounted under `/v1` (e.g.
    `POST /v1/memories/ingest`, `PUT /v1/agents/trust`). The unversioned
    `/health` liveness probe is unchanged.
  - Workspace `GET /memories/list`, `GET /memories/:id`, and
    `DELETE /memories/:id` now require `agent_id` when `workspace_id`
    is present; missing returns 400 (no visibility-unsafe fallback).
  - `PUT /memories/config` returns 410 Gone in production. Provider/model
    fields (embedding_provider, embedding_model, llm_provider, llm_model)
    are rejected with 400 — they were never honored mid-flight (provider
    caches are fixed at first use). Set via env at process start.
  - npm package renamed `@atomicmemory/atomicmemory-engine` →
    `@atomicmemory/atomicmemory-core`. Tarball now ships `dist/` (built
    via `tsc`); `main`/`types`/`exports` point at compiled output.
  - Deep-importers of `services/embedding` and `services/llm` must call
    `initEmbedding(config)` / `initLlm(config)` before hot-path APIs.
    Consumers using `createCoreRuntime({ pool })` are auto-initialized.

  Rearchitecture (Phases 1A–7)
  - Phase 1A: composition root via `createCoreRuntime` + `createApp` (#8)
  - Phase 2A: canonical search scope contract (#9)
  - Phase 2B: explicit retrieval observability contract (#10)
  - Phase 3: runtime-config seam cleanup to Phase 4 boundary (#11)
  - Phase 4: ingest pipeline decomposition (475 → 215 lines) (#13)
  - Post-Phase 4: unify scope contract via `scopedSearch`/`scopedExpand`,
    document schema scoping gaps as deferred (#15)
  - Phase 5: narrow repository access behind 8 domain-facing stores;
    workspace ingest now flows through canonical lineage (#16)
  - Phase 6: publish stable consumption seams (HTTP, in-process, docker)
    with two-direction parity contract test (#17)
  - Phase 7 Steps 3a–3c: split runtime config into supported/internal
    partitions; deprecate `PUT /memories/config` for production (#18)
  - Phase 7 Step 3d: thread config through 5 leaf modules (33→28
    singleton audit) (#21)
  - Phase 7 Item 4: retrieval polish — `memory-search.ts` reduced to
    pure orchestration (374 → 248 lines, -34%) (#22)
  - Chore: reduce fallow duplication 367 → 234 lines (#12)

  OSS release prep
  - `"private": true` removed; package renamed to
    `@atomicmemory/atomicmemory-core`. `files` field scopes the tarball.
  - `tsconfig.build.json` + `prepublishOnly` so `npm publish` always ships
    compiled `dist/`. Bare-import smoke test passes.
  - `release.yml` publishes to public npm on tag push (NPM_TOKEN secret).
  - SuperMem codename scrubbed from `src/`, tests, docker-compose, and
    `.env.example` (DB user/name/password renamed to `atomicmemory`).
  - Private-research-repo URLs unlinked from public docs.
  - README links to docs.atomicmemory.ai.
  - `/v1` API prefix on all routes; mount-coverage test added.
  - CI workflow: set `CORE_RUNTIME_CONFIG_MUTATION_ENABLED=true` to
    match `.env.test` (gitignored) and unblock the composed-boot-parity
    test on `PUT /v1/memories/config`.

  Verification
  - 966/966 tests pass (100 files)
  - npx tsc --noEmit clean
  - fallow --no-cache: 0 above threshold (maintainability 91.0)
  - npm publish --dry-run succeeds
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant